﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace WindowIconizer
{
    delegate void callbackFromKeyboard(bool isOK);
    delegate void callbackFromMouse(bool isOK, int x, int y);

    public partial class Form1 : Form
    {
        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        private struct HWND__
        {
            /// int
            public int unused;
        }

        [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
        private struct Point
        {
            /// LONG->int
            public int x;

            /// LONG->int
            public int y;
        }

        /// Return Type: HWND->HWND__*
        ///Point: POINT->tagPOINT
        [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "WindowFromPoint")]
        private static extern System.IntPtr WindowFromPoint(Point Point);

        /// Return Type: HWND->HWND__*
        ///hWnd: HWND->HWND__*
        [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "GetParent")]
        private static extern System.IntPtr GetParent([System.Runtime.InteropServices.InAttribute()] System.IntPtr hWnd);

        /// Return Type: BOOL->int
        ///hWnd: HWND->HWND__*
        ///nCmdShow: int
        [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "ShowWindow")]
        [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
        private static extern bool ShowWindow([System.Runtime.InteropServices.InAttribute()] System.IntPtr hWnd, int nCmdShow);

        /// Return Type: int
        ///hWnd: HWND->HWND__*
        ///lpString: LPSTR->CHAR*
        ///nMaxCount: int
        [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "GetWindowTextA")]
        private static extern int GetWindowTextA([System.Runtime.InteropServices.InAttribute()] System.IntPtr hWnd, [System.Runtime.InteropServices.OutAttribute()] [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)] System.Text.StringBuilder lpString, int nMaxCount);

        [DllImport("user32.dll")]
        private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint ProcessId);

        //constructor and destructor also take care of hooks
        public Form1()
        {
            InitializeComponent();

            globalKeyboardHook.setKeyboardCallback(new callbackFromKeyboard(keyboardHandler));
            globalMouseHook.setMouseCallback(new callbackFromMouse(mouseHandler));

            globalKeyboardHook.start();
            globalMouseHook.start();
        }
        ~Form1()
        {
            globalKeyboardHook.stop();
            globalMouseHook.stop();
        }

        //general window handle functions
        System.IntPtr getTopParentWindowHandle(int x, int y)
        {
            Point p;
            p.x = x;
            p.y = y;

            System.IntPtr hwnd = WindowFromPoint(p);
            System.IntPtr phwnd = GetParent(hwnd);

            while (phwnd != IntPtr.Zero)
            {
                hwnd = phwnd;
                phwnd = GetParent(hwnd);
            }
            return hwnd;
        }
        string getProcessPathFromWindowHandle(IntPtr hwnd, out uint pid)
        {
            pid = 0;
            GetWindowThreadProcessId(hwnd, out pid);
            Process p = Process.GetProcessById((int)pid);
            return p.MainModule.FileName;
        }

        //user input
        private bool keyboardOK = false;
        private void keyboardHandler(bool isOK)
        {
            keyboardOK = isOK;
        }
        private void mouseHandler(bool isOK, int x, int y)
        {
            bool mouseOK = isOK;

            if (keyboardOK && mouseOK)
            {
                uint pid = 0;
                IntPtr handle = getTopParentWindowHandle(x, y);
                String exePath = getProcessPathFromWindowHandle(handle, out pid);
                String exeName = exePath.Substring(exePath.LastIndexOf("\\") + 1);

                bool isDuplicate = false;
                bool isSelf = (pid == (uint)Process.GetCurrentProcess().Id);
                for (int i = 0; i < dataGridView1.Rows.Count; i++)
                {
                    isDuplicate = isDuplicate || (handle == (IntPtr)dataGridView1.Rows[i].Cells[0].Value);
                }

                if (!isDuplicate && !isSelf)
                {
                    StringBuilder windowText = new StringBuilder(1024);
                    GetWindowTextA(handle, windowText, 1024);

                    int n = dataGridView1.Rows.Add();
                    dataGridView1.Rows[n].Cells[0].Value = handle;
                    dataGridView1.Rows[n].Cells[1].Value = exeName;
                    dataGridView1.Rows[n].Cells[2].Value = windowText;

                    hideWindow(handle);
                }
            }
        }

        //prevent dataGridView from selecting first row if there is no data
        private void dataGridView1_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
        {
            if (dataGridView1.Rows.Count == 0)
            {
                dataGridView1.Rows[e.RowIndex].Selected = false;
            }
        }

        //window magic:)
        void hideWindow(IntPtr handle)
        {
            //make window disappear - minimize needs to happen before hide in order to make
            //this work in all cases. Otherwise, it sometimes won't work.
            const int SW_HIDE = 0;
            const int SW_MINIMIZE = 6;
            ShowWindow(handle, SW_MINIMIZE);
            ShowWindow(handle, SW_HIDE);
        }
        void unhideWindow(IntPtr handle)
        {
            //make window appear
            const int SW_SHOW = 5;
            const int SW_RESTORE = 9;
            ShowWindow(handle, SW_RESTORE);
            ShowWindow(handle, SW_SHOW);
        }

        private void dataGridView1_CellContentDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            IntPtr handle = (IntPtr)dataGridView1.Rows[e.RowIndex].Cells[0].Value;

            unhideWindow(handle);

            dataGridView1.Rows.RemoveAt(e.RowIndex);
        }
        private void Form1_Resize(object sender, EventArgs e)
        {
            if (WindowState == FormWindowState.Minimized)
            {
                Hide();
            }
        }
        private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            Show();
            WindowState = FormWindowState.Normal;
        }

        //deal with closing forms
        bool reallyClose = false;
        private void closeApplicationToolStripMenuItem_Click_1(object sender, EventArgs e)
        {
            //make all windows visible before closing
            for (int i = 0; i < dataGridView1.Rows.Count; i++)
            {
                IntPtr handle = (IntPtr)dataGridView1.Rows[i].Cells[0].Value;

                unhideWindow(handle);
            }

            //we have to set reallyClose to true because calling Close() will call
            //the Form1_FormClosing() function
            reallyClose = true;
            Close();
        }
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            //reallyClose allows us to handle the user pressing the X button and
            //the user clicking Close Application in the context menu in different ways
            if (!reallyClose)
            {
                e.Cancel = true;
                WindowState = FormWindowState.Minimized;
                Hide();
            }
        }
    }
}
